
#include <math.h>
#include <vector>
#include <typeinfo>
#include <apr_date.h>
#include "../crypt/ECDSAPublicKey.h"
#include "../crypt/ECDSAPrivateKey.h"
#include "../crypt/DSAPublicKey.h"
#include "../crypt/DSAPrivateKey.h"
#include "../crypt/RSAPublicKey.h"
#include "../crypt/RSAPrivateKey.h"
#include "../crypt/AESSymmetricKey.h"
#include "../iface/APRInterface.h"
#include "../utils/StringUtils.h"
#include "../net/netgeneral.h"
#include "KeyTableView.h"
#include "KeyDB.h"
#include "Key.h"

/** Constructs the view for this key table.
 * 
 * @param nkdb The key database pointer that points to the key
 * database to be connected to.
 * 
 * @param ikeytableusetype A flag denoting the use scenario for keys
 * in this table. One of KeyTableView::keytableusetype.
 * 
 * @param itablename The name of the table that this class is to be a
 * view of.
 * 
 * @param itableWIPpwd The work-in-progress password to be used for
 * reading and writing secret keys to this table.
 * 
 * @param iresidentkeyname The name of the key(s) to be used to
 * identify the main actor.
 * 
 * @param iresidentkeypwd The password to be used to decrypt the key
 * of the main actor. */
KeyTableView::KeyTableView(KeyDB *nkdb, Key::keypurposetype ikeytableusetype,
   const char *itablename, const char *itableWIPpwd,
   const char *iresidentkeyname, const char *iresidentkeypwd)
{
   nkeydb = nkdb;
   keytableusetype = ikeytableusetype;
   tablename = (itablename) ? strdup(itablename) : NULL;
   tableWIPpwd = (itableWIPpwd) ? strdup(itableWIPpwd) : NULL;
   residentkeyname = (iresidentkeyname) ? strdup(iresidentkeyname) : NULL;
   
   if (residentkeyname)
   {
      Key tkey(nkeydb, iresidentkeyname, keytableusetype);
      Key::keyopenerror errval = Key::KEY_OPEN_ERROR__KEY_NOT_FOUND;
      ownkey = tkey.getKey((iresidentkeypwd) ? iresidentkeypwd : itableWIPpwd, &errval);
      // If the key is actually not found, then the keys could be
      // generated
      couldgeneratekeysflag = (errval == Key::KEY_OPEN_ERROR__KEY_NOT_FOUND);
      Key::showKeyOpenError(iresidentkeyname, keytableusetype, errval);
   } else
   {
      couldgeneratekeysflag = 0;
      ownkey = NULL;
   }
}

/** The destructor for the key table view. */
KeyTableView::~KeyTableView()
{
   if (tablename) free(tablename);
   tablename = NULL;
   if (tableWIPpwd)
      StringUtils::razeString(&tableWIPpwd);
   if (residentkeyname) free(residentkeyname);
   residentkeyname = NULL;
   if (ownkey) delete ownkey;
   ownkey = NULL;
}

/** Determine if the named key exists.
 * 
 * @param keyname The name of the key that may exist.
 * 
 * @return As this is a unique key, one if it does exist, zero if it
 * does not. */
int KeyTableView::testKeyExists(const char *keyname) const
{
   int keycount;
   if (keyname == NULL) return 0;
   nkeydb->sqlGetSingleIntf(&keycount,
      "select count(keyname) from '%s' where keyname='%s';", tablename, keyname);
   return keycount;
}

/** Get the owner of the key.
 * 
 * @param keyname The name of the key whose owner need determining.
 * 
 * @return The owner, or NULL if it can't be found. */
char *KeyTableView::getKeyOwner(const char *keyname) const
{
   if (keyname == NULL) return NULL;
   char *owner_name = NULL;
   nkeydb->sqlGetSingleStringf(&owner_name,
      "select owner from '%s' where keyname='%s';", tablename, keyname);
   return owner_name;
}

/** Delete the key with the given name.
 * 
 * @param keyname The key to delete. */
void KeyTableView::deleteKey(const char *keyname) const
{
   nkeydb->sqlInstantExecf("delete from '%s' where keyname='%s';", tablename, keyname);
}

/** Count the number of keys in the key chain.
 * 
 * @param owner The actor whose key chain is to be counted.
 * 
 * @return The number of keys in the key chain. */
int KeyTableView::countKeyChainOfOwner(const char *owner) const
{
   int keycount = 0;
   nkeydb->sqlGetSingleIntf(&keycount,
      "select count(keyname) from '%s' where owner='%s';", tablename, owner);
   return keycount;
}

/** Get the name of latest key in the specified chain.
 * 
 * @param owner The owner of the key whose name is to be found.
 * 
 * @return The name of the latest key. The returned pointer should be
 * freed by the caller. */
char *KeyTableView::getLatestKeyNameOfOwner(const char *owner) const
{
   char *result = NULL;
   nkeydb->sqlGetSingleStringf(&result,
      "select keyname from '%s' where owner='%s' and keymark like '%%%c%%';", tablename, owner, KeyDB::KEYMARK_CHAINLATEST);
   if (result) return result;
   nkeydb->sqlGetSingleStringf(&result,
      "select keyname from '%s' where owner='%s' and keymark like '%%%c%%';", tablename, owner, KeyDB::KEYMARK_CHAINROOT);
   if (result) return result;
   int chaincount = countKeyChainOfOwner(owner);
   if (countKeyChainOfOwner(owner) > 0)
   {
      errprint("%s: there are %d keys owned by '%s' but none are directly latest - fixme broken keymark",
         tablename, chaincount, owner);
      std::vector<char *> *keynamedate_info =
         nkeydb->sqlScanAllTextf("select keyname,keymark from '%s' where owner='%s';", tablename, owner);
      if ((keynamedate_info == NULL) || (keynamedate_info->size() == 0))
      {
         errprint("%s: could not get keys owned by '%s', possible corruption", tablename, owner);
         if (keynamedate_info) delete keynamedate_info;
         return NULL;
      }
      apr_time_t minimum = 0;
      int chosenslot = -1;
      apr_time_t timet = apr_time_now();
      for (int i = 0; i < chaincount; i++)
      {
         apr_time_t timebefore = apr_date_parse_rfc(keynamedate_info->at(i*2 + 1));
         if ((timet - timebefore) < minimum)
         {
            minimum = (timet - timebefore);
            chosenslot = i;
         }
      }
      if (chosenslot == -1)
      {
         errprint("%s: corrupted or broken dates in key chain '%s'", tablename, owner);
         StringUtils::destroyStringList(keynamedate_info);
         return NULL;
      }
      errprint("%s: found latest key owned by '%s' dated '%s': is latest",
         tablename, owner, keynamedate_info->at(chosenslot*2 + 1));
      char *namedup = strdup(keynamedate_info->at(chosenslot*2 + 0));
      StringUtils::destroyStringList(keynamedate_info);
      return namedup;
   }
   errprint("%s: no keys found with owner '%s'", tablename, owner);
   return NULL;
}

/** Retry loading the key for the main actor. This is to be called
 * after a key generation step.
 * 
 * @param ownpwd The password required to open the main key. */
void KeyTableView::retryGettingOwnKeys(const char *ownpwd)
{
   if (couldgeneratekeysflag)
   {
      Key tkey(nkeydb, residentkeyname, keytableusetype);
      Key::keyopenerror errval = Key::KEY_OPEN_ERROR__KEY_NOT_FOUND;
      ownkey = tkey.getKey((ownpwd) ? ownpwd : tableWIPpwd, &errval);
      if (errval != Key::KEY_OPEN_ERROR__KEY_NOT_FOUND)
      {
         if (errval != Key::KEY_OPEN_ERROR__NO_ERROR)
            Key::showKeyOpenError(residentkeyname, keytableusetype, errval);
         couldgeneratekeysflag = false;
      }
   }
}

/** Traverse the key chain to get to the latest updated keys.
 * 
 * @param ownpwd The password required for the key(s). */
void KeyTableView::updateToLatestOwnKey(const char *ownpwd)
{
   SQLiteAutoLock autolock(nkeydb->getAutoLock());
   char *latest_key_name = getLatestKeyNameOfOwner(nkeydb->getOwnerName());
   Key tkey(nkeydb, latest_key_name, keytableusetype);
   Key::keyopenerror errval = Key::KEY_OPEN_ERROR__KEY_NOT_FOUND;
   ownkey = tkey.getKey(ownpwd, &errval);
   if (errval != Key::KEY_OPEN_ERROR__NO_ERROR)
   {
      Key::showKeyOpenError(latest_key_name, keytableusetype, errval);
   } else
   {
      if (residentkeyname) free(residentkeyname);
      residentkeyname = (latest_key_name) ? strdup(latest_key_name) : NULL;
   }
   free(latest_key_name);
}

/** Perform update with external keys.
 * 
 * @param key_filename Filename for file containing the key name.
 * 
 * @param WIPpwd Work-In-Progress password (due to the bespoke
 * initialisation, this isn;t otherwise available).
 * 
 * @param mp APR memory pool to use. */
int KeyTableView::updateKeyChainUsingFile(const char *key_filename, const char *WIPpwd, apr_pool_t *mp)
{
   std::vector<char *> setofstrings;
   char *key_file_string = NULL;
   StringUtils::readFileToString(&key_file_string, key_filename, mp);
   key_file_string = StringUtils::replaceWithPoolString(key_file_string, mp);
   if (key_file_string == NULL) { errprint("No '%s' key updates to apply...", tablename); return 1; }
   if (strlen(key_file_string) == 0) { errprint("Could not write brokerkey to '%s' (is the broker running/do we have network?)", tablename); return 1; }
   while (strlen(key_file_string) > 0)
   {
      const char *divider = CRLF_STR;
      char *substringbegin = strstr(key_file_string, divider);
      setofstrings.push_back(key_file_string);
      if (setofstrings.size() == 5) break;
      if (substringbegin)
      {
         substringbegin[0] = '\0';
         key_file_string = &(substringbegin[strlen(divider)]);
      }
   }
   const char *thiskey_name = setofstrings.at(0);
   const char *thiskey_type = setofstrings.at(1);
   const char *thiskey_date = setofstrings.at(2);
   const char *thiskey_owner = setofstrings.at(3);
   const char *thiskey_text = setofstrings.at(4);
   if (testKeyExists(thiskey_name))
   {
      errprint("No new key updates to apply to table '%s' (key '%s' exists)...", tablename, thiskey_name);
      return 0;
   }
   // Key is valid, add it to the end of the key chain.
   char *latest_key_name = StringUtils::replaceWithPoolString(getLatestKeyNameOfOwner(thiskey_owner), mp);
   if (latest_key_name != NULL)
   {
      Key t_tkey(nkeydb, latest_key_name, keytableusetype);
      if (t_tkey.checkKeyMark(KeyDB::KEYMARK_CHAINLATEST))
      {
         t_tkey.removeKeyMark(KeyDB::KEYMARK_CHAINLATEST);
         t_tkey.addKeyMark(KeyDB::KEYMARK_CHAIN, 1);
         t_tkey.commit();
      }
   }
   Key::keyopenerror terr;
   
   AbstractKey *a_newkey = Key::tryToGetKeyFromText(thiskey_type, NULL,
      thiskey_text, thiskey_name, &terr);
   Key::showKeyOpenError(latest_key_name, keytableusetype, terr);
   switch (keytableusetype)
   {
      case Key::KEYPURPOSE_ENCRYPTION:
      {
         try
         {
            Key t_newkey(nkeydb, *(dynamic_cast<EncryptionKey *>(a_newkey)), WIPpwd);
            t_newkey.setDateCreated(thiskey_date);
            t_newkey.setOwner(thiskey_owner);
            t_newkey.addKeyMark(KeyDB::KEYMARK_CHAINLATEST, 1);
            t_newkey.commit();
         }
         catch (std::bad_cast &bc)
         {
            errprint("new key was not compatible with the task of encryption");
         }
         break;
      }
      case Key::KEYPURPOSE_DECRYPTION:
      {
         try
         {
            Key t_newkey(nkeydb, *(dynamic_cast<DecryptionKey *>(a_newkey)), WIPpwd);
            t_newkey.setDateCreated(thiskey_date);
            t_newkey.setOwner(thiskey_owner);
            t_newkey.addKeyMark(KeyDB::KEYMARK_CHAINLATEST, 1);
            t_newkey.commit();
         }
         catch (std::bad_cast &bc)
         {
            errprint("new key was not compatible with the task of decryption");
         }
         break;
      }
      case Key::KEYPURPOSE_SIGNING:
      {
         try
         {
            Key t_newkey(nkeydb, *(dynamic_cast<SigningKey *>(a_newkey)), WIPpwd);
            t_newkey.setDateCreated(thiskey_date);
            t_newkey.setOwner(thiskey_owner);
            t_newkey.addKeyMark(KeyDB::KEYMARK_CHAINLATEST, 1);
            t_newkey.commit();
         }
         catch (std::bad_cast &bc)
         {
            errprint("new key was not compatible with the task of signing");
         }
         break;
      }
      case Key::KEYPURPOSE_CHECKING:
      {
         try
         {
            Key t_newkey(nkeydb, *(dynamic_cast<CheckingKey *>(a_newkey)), WIPpwd);
            t_newkey.setDateCreated(thiskey_date);
            t_newkey.setOwner(thiskey_owner);
            t_newkey.addKeyMark(KeyDB::KEYMARK_CHAINLATEST, 1);
            t_newkey.commit();
         }
         catch (std::bad_cast &bc)
         {
            errprint("new key was not compatible with the task of checking");
         }
         break;
      }
   }
   return 0;
}

/** Deleting keys remotely decrements the existsrefcount field
 * counter.
 * 
 * @param keyname Name of the key that is deleted from the remote
 * machine. */
void KeyTableView::decrementKeyExistsReferenceCount(const char *keyname) const
{
   nkeydb->sqlInstantExecf("update '%s' set existsrefcount=existsrefcount-1 where keyname='%s';", tablename, keyname);
}

/** Adding keys remotely increments the existsrefcount field counter.
 * 
 * @param keyname Name of the key that is added to the remote
 * machine. */
void KeyTableView::incrementKeyExistsReferenceCount(const char *keyname) const
{
   nkeydb->sqlInstantExecf("update '%s' set existsrefcount=existsrefcount+1 where keyname='%s';", tablename, keyname);
}

/** Get the existsrefcount field counter.
 * 
 * @param keyname Name of the key that needs to be queried on the
 * remote machine.
 * 
 * @return Returned reference count. */
int KeyTableView::getKeyExistsReferenceCount(const char *keyname) const
{
   int exrefcount = 0;
   nkeydb->sqlGetSingleIntf(&exrefcount, "select 'existsrefcount' from '%s' where keyname='%s';", tablename, keyname);
   return exrefcount;
}

/** Stopping the use of keys decrements the usedrefcount field
 * counter.
 * 
 * @param keyname Name of the key that is no longer used to access the
 * remote machine. */
void KeyTableView::decrementKeyUsedReferenceCount(const char *keyname) const
{
   nkeydb->sqlInstantExecf("update '%s' set usedrefcount=usedrefcount-1 where keyname='%s';", tablename, keyname);
}

/** Starting using a key to access a remote machine increments the
 * usedrefcount field counter.
 * 
 * @param keyname Name of the key that is now used to access the
 * remote machine. */
void KeyTableView::incrementKeyUsedReferenceCount(const char *keyname) const
{
   nkeydb->sqlInstantExecf("update '%s' set usedrefcount=usedrefcount+1 where keyname='%s';", tablename, keyname);
}

/** Get the usedrefcount field counter.
 * 
 * @param keyname Name of the key that needs to be queried on the
 * remote machine.
 * 
 * @return Returned reference count. */
int KeyTableView::getKeyUsedReferenceCount(const char *keyname) const
{
   int usedrefcount = 0;
   nkeydb->sqlGetSingleIntf(&usedrefcount, "select 'usedrefcount' from '%s' where keyname='%s';", tablename, keyname);
   return usedrefcount;
}
